#define PERSISTANT_AFTER_PARSE_FAILURE
#define THROW_ON_PARSE_EXCEPTION

using System;
using System.Collections.Generic;

namespace SharpMud.LPC
{
	public abstract class Object : ICloneable
	{
		#region Constructors
		protected Object()
		{
		}
		#endregion

		#region Static Stuff
		public static LPC.Object[] Parse(string text)
		{
            int index = 0;
            return Parse(text.ToCharArray(), ref index);
		}
        public static LPC.Object[] Parse(string text, ref int index)
        {
            int i2 = index;
            LPC.Object[] results;
            results = Parse(text.ToCharArray(i2, (text.Length - (i2 + 1))), ref i2);
            index = index + i2;
            return results;
        }
        public static LPC.Object[] Parse(char[] text, ref int index)
        {
            return Parse(text, ref index, int.MaxValue);
        }
        internal static LPC.Object[] Parse(char[] text, ref int index, int maxToGet)
        {
            //Its dirty, and its brute force, but it keeps from having to nest loops and if statements
            //endlessly in this function

            //Basically using delegates to try parsing the input in different forms, whose functions
            //update the local loop's index which is passed by-ref

            //In a specific order to prevent conflicts from occuring, so that individual parse
            //methods can be as simple as possible
            List<TryParseCharacters<LPC.Object>> parsers = new List<TryParseCharacters<LPC.Object>>();
            parsers.Add(new TryParseCharacters<LPC.Object>(LPC.Array.TryParse));
            parsers.Add(new TryParseCharacters<LPC.Object>(LPC.MappingEntry.TryParse));
            parsers.Add(new TryParseCharacters<LPC.Object>(LPC.String.TryParse));
            parsers.Add(new TryParseCharacters<LPC.Object>(LPC.Integer.TryParse));

            //This will hold the results from multiple invocations of the multiple parsers
            List<LPC.Object> results = new List<LPC.Object>();


            char cc;
            //Loop through the characters in the string given
            cc = text[0];
            for (int i = 0; i < text.Length; i++, cc = text[i])
            {

                //If its any kind of whitespace, we don't need to linger here
                while (i<text.Length && Char.IsWhiteSpace(text[i]))
                    i++;
                //If its not one of a set of characters we know *have* to be in an LPC object,
                //then there is no use lingering here
                if (!(
                       Char.IsLetterOrDigit(cc)
                    || cc == '\"'
                    || cc == '\''
                    || cc == '(' || cc == '{'
                    || cc == ')' || cc == '}'
                    || cc == ':'
                        ))
                    continue;

                //Holds the result expected from the for...each loop
                LPC.Object currentResult = null;

                //Loop through all the parsers and see if any can make sense of what is at the
                //current character.
                foreach (TryParseCharacters<LPC.Object> parse in parsers)
                {
                    int i2 = i;
                    bool success;


#if !THROW_ON_PARSE_EXCEPTION
                    //Eats the exception if we don't want to worry about those
                    try
                    {
#endif
                    success = parse(text, ref i2, out currentResult);
#if !THROW_ON_PARSE_EXCEPTION
                    }
                    catch(System.Exception e)
                    {
                        success = false;
                    }
#endif

#if !PERSISTANT_AFTER_PARSE_FAILURE
                    if (!success)
                    {
                        //Sets the index at the end of the unsuccessfully parsed text,wherever the parser gave up
                        i = i2;
                    }
#endif

                    //If the parse function said it succeeded, we should have a result, and otherwise, we shouldn't.
                    //And vice versa.
                    System.Diagnostics.Debug.Assert((currentResult != null) == success, "(currentResult != null) == success");

                    if (success)
                    {
                        //If we succeeded with the current parser, no need to try others
                        break;
                    }
                }
                if (currentResult != null)
                    results.Add(currentResult);
                if (results.Count >= maxToGet)
                    return results.ToArray();
            }
            return results.ToArray();
        }
		#endregion

		#region Public Methods
		/// <summary>
		/// This method, when overidden in a deriving class, expresses this LPC object as an appropriately
		/// formatted string for the given type.
		/// </summary>
		/// <returns>Returns value of type string</returns>
		public abstract string ToParsableString();
		#endregion

        #region Internal Statics
        
        #endregion

        #region ICloneable Members

        object ICloneable.Clone()
        {
            return (object)this.Clone();
        }

        protected abstract Object Clone();

        #endregion
    }

    public class InvalidLpcFormatException : System.Exception
    {
        private const string defaultMessage = "The format of the LPC Object's string representation is invalid.";

        public InvalidLpcFormatException()
            : base(defaultMessage)
        {
        }

        public InvalidLpcFormatException(System.Exception innerException)
            : base(defaultMessage,innerException) { }
    }

    internal class ParseUtil
    {
        public const string ArrayStart = "({";
        public const string ArrayEnd = "})";


    }

    public delegate bool TryParseString<T>(string text, ref int index, out T result);
    public delegate bool TryParseCharacters<T>(char[] text, ref int index, out T result);
}